import asyncio
import datetime
import random
import time
import math
import aiohttp

import os
import iio
import numpy as np
from multiprocessing import Pool

from common.third_party.devmem import DevMem, DevMemCache
from common.third_party.save_config import load_config, save_config,  load_const, save_const
from services.ad936x.api import Ad936xApi, DoubleGain, DoubleAttenuation, DoubleFrequency
from services.automatic_control.automatic_loops.carrier_frequency_automatic_control import \
    CarrierFrequencyAutomaticControl
from services.automatic_control.schemas.configs import PilotTelemetry
from services.server_integration.api import ServerIntegrationApi
from services.relays_module.api import RelaysModuleApi
from services.attenuators.api import AttenuatorsApi
from services.control.api import ControlApi
from services.services_module.api import ServicesModuleApi
from services.server_integration.schemas.unit_configuration import ChanelConfig
from services.server_integration.schemas.calibrate_status import CalibrateMessage, CalibrateMessageKInd, CalibrateMessageActionKind, CalibrateHistoryMessage, CalibrateHistory
from services.server_integration.schemas.language_string import LanguageString
from services.dsp.api import DspApi
from services.ocb.api import OcbApi
from services.automatic_control.ppc import PilotPowerCalculation
from settings import settings
from common.third_party.log import new_logger
import logging


CMK = CalibrateMessageKInd


SM_TEMPERATURE_TABLE = {
    -60: -2.4,
    -55: -2.2,
    -50: -2.0,
    -45: -1.7,
    -40: -1.5,
    -35: -1.3,
    -30: -1.2,
    -25: -1.0,
    -20: -0.7,
    -15: -0.6,
    -10: -0.5,
    -5: -0.5,
    0: -0.4,
    5: -0.3,
    10: -0.2,
    15: -0.2,
    20: -0.1,
    25: -0.1,
    30: 0,
    35: 0.2,
    40: 0.3,
    45: 0.5,
    50: 0.8,
    55: 1.0,
    60: 1.2,
    65: 1.5,
    70: 1.7,
    75: 1.9,
    80: 2.1,
    85: 2.4,
    90: 2.6,
    95: 2.8,
    100: 3.0,
    105: 3.2,
    110: 3.4,
    115: 3.5,
    120: 3.6,
    125: 3.7,
    130: 3.8,
    135: 3.8,
    140: 3.9,
    145: 3.9,
    150: 4.0,
    155: 4.2,
    160: 4.4,
}

SM_TEMPERATURE_TABLE2 = {
    100: 0.7,
    95: 0.5,
    80: 0.2,
    75: -0.2,
    70: -0.6,
    65: -1.3,
    60: -1.6,
    55: -2.0,
    50: -1.7,
    45: -1.4,
    40: -1.0,
    35: -0.7,
    30: -0.3,
    25: -0.4,
    20: -0.5,
    15: -0.6,
    10:  0.7,
    5: 0.9,
    0: 1.3,
    -5: 1.5,
    -10: 1.6,
    -15: 1.8,
    -20: 2.0,
    -25: 2.2,
    -30: 2.5,
    -35: 2.6,
    -40: 3.0,
    -45: 3.1,
    -50: 3.4,
    -55: 3.6,
    -60: 4.0,
    -65: 4.3,
    -70: 4.6,
}

RM_TX_TABLE = {
    85: 2,
    80: 1,
    75: 0,
    70: 0,
    65: 0,
    60: 0,
    55: -1.0,
    50: -1.5,
    45: -2,
    40: -2.5,
    35: -3,
    30: -3,
    25: -4,
}

def clamp(max_value: float, min_value: float, num: float, ) -> float | int:
    return max(min(num, max_value), min_value)

class AutomaticControl:
    AD_API = Ad936xApi()
    CONTROL_API = ControlApi()
    ATT_API = AttenuatorsApi()
    RM_API = RelaysModuleApi()
    SM_API = ServicesModuleApi()
    SI_API = ServerIntegrationApi()
    DSP_API = DspApi()
    OCB_API = OcbApi()

    SERVICE_CLOCK = 0x43C5_0000
    GENERATOR_ADDR = 0x83C1_0000
    TCL = [0x83E0_0000, 0x83E1_0000, 0x83E5_0000]

    RX_GAIN = 15
    TX_ATTENUATION = 30
    TX_SERVICE_ATTENUATION = 30
    PILOT_POWER_AVERAGE_LEN = 16
    PILOT_POWER_AVERAGE_RX_AC_LEN = 6 * 5
    POWER_DELTA_STEP = 0.1

    RX_RM_MEMORY_SIZE = 16

    __slots__ = [
        'link_tx_loop_state', 'link_rx_loop_state', 'service_tx_loop_state', 'service_rx_loop_state',
        'loop_state', 'adjustment_state', 'target_pilots_power', 'target_power_delta', '_carrier_frequency_ac',
        'base_statin_calibration_state', 'pilot_telemetry', '_all_loop_state',
        'pilot_power_average', 'current_power_delta',
        'target_power_delta_offsets', 'current_pilot_power',
        'sm_base_power_offsets', 'sm_base_power',
        'rm_base_power_offsets', 'rm_base_power',
        'output_power_correct_number', 'additional_output_power_correct_number',
        '_mem', 'ppc',
        'tx_control_attenuation_lack',
        '_logger_link', '_logger_service', '_logger_bs',
        '_service_tx_control_lock',
        '_need_fast_tx_service_ac',
        '_calibrate_loger',
        '_calibrate_server_sr', '_calibrate_server_sw',
        '_logger_rm', '_cmg_lock', 'last_calibrate_history',
        'is_stop_calibration', '_rx_rm_memory',
        '_linx_tx_prev_aten', '_carrier_frequency_control_enable', 'calibration_in_progress', 'calibration_start_time', '_correct_pilot_in_work',
        'sm_paps'
    ]

    def __init__(self):
        self._logger_link = new_logger('ac-lin', level=logging.ERROR, stdout_handler=False)
        self._logger_service = new_logger('ac-service', level=logging.ERROR, stdout_handler=False)
        self._logger_bs = new_logger('bs-power', level=logging.ERROR, stdout_handler=False)

        self._mem = DevMemCache()

        self._carrier_frequency_control_enable = True
        self._linx_tx_prev_aten: list[int] = load_config('ac/linx_tx_prev_aten', list, [23, 23])
        self.link_tx_loop_state: bool = load_config('ac/link_tx_loop_state', bool, False)
        self.link_rx_loop_state: bool = load_config('ac/link_rx_loop_state', bool, False)
        self.service_tx_loop_state: bool = load_config('ac/service_tx_loop_state', bool, False)
        self.service_rx_loop_state: bool = load_config('ac/service_rx_loop_state', bool, False)

        self.loop_state: bool = load_config('ac/loop_state', bool, False)
        self.adjustment_state: bool = load_config('ac/adjustment_state', bool, False)
        self.target_power_delta: list[float] = [
            load_config(f'ac/target_power_delta{i}', float, 0.0)
            for i in range(settings.DMA_POWER_METERS_NUMBER)
        ]
        self.target_power_delta_offsets: list[float] = [
            load_config(f'ac/target_power_delta_offsets{i}', float, 0.0)
            for i in range(settings.DMA_POWER_METERS_NUMBER)
        ]
        self.target_pilots_power: list[float] = [
            load_config(f'ac/target_pilot_power_{i}', float, 0.0)
            for i in range(settings.AUTOMATIC_CONTROL_PILOT_POWER_NUMBER)
        ]
        self.sm_base_power_offsets: list[float] = load_config('ac/sm_base_power_offsets', list[float], [0.0] * 4)
        self.sm_base_power: list[float] = load_config('ac/sm_base_power', list[float], [0.0] * 4)

        self.rm_base_power: list[float] = load_config('ac/rm_base_power', list[float], [0.0] * 2)
        self.rm_base_power_offsets: list[float] = load_config('ac/rm_base_power_offsets', list[float], [0.0] * 2)

        self.output_power_correct_number: list[float] = [25 + 12.7, 25 + 12.7, 20, 20, 19, 19]
        # self.output_power_correct_number: list[float] =\
        #     load_const('ac/output_power_correct_number', list[float], [25, 25, 20, 20, 20, 20]) # fsc bs == 56
        self.additional_output_power_correct_number: list[float] =\
            load_const('ac/additional_output_power_correct_number', list[float], [0.0] * 6)

        self.sm_paps = [0.0] * 4

        self._rx_rm_memory = []
        self.base_statin_calibration_state = load_config('ac/base_statin_calibration_state', bool, False)

        self.tx_control_attenuation_lack = [0, 0]

        self.pilot_telemetry = PilotTelemetry()
        self.current_pilot_power = [[] for _ in range(2)]

        self._all_loop_state = True
        self.pilot_power_average = [[] for _ in range(4)]
        self.current_power_delta = [0.0] * 6
        self._carrier_frequency_ac = CarrierFrequencyAutomaticControl()
        self.ppc = PilotPowerCalculation()
        self._need_fast_tx_service_ac = False

        self._service_tx_control_lock = asyncio.Lock()
        self._cmg_lock = asyncio.Lock()

        self._calibrate_server_sr: dict[str, asyncio.StreamReader] = dict()
        self._calibrate_server_sw: dict[str, asyncio.StreamWriter] = dict()

        self._correct_pilot_in_work = [False, False]

        self.last_calibrate_history: CalibrateHistory = load_config('ac/last_calibrate_history', CalibrateHistory, CalibrateHistory())

        self.is_stop_calibration = False
        self.calibration_in_progress = False
        self.calibration_start_time = None

    async def read_true_power(self, index: int) -> float:
        block_state = await self.CONTROL_API.get_control_state('block_state')
        # is_gsm_mode = \
        #     await self.SI_API.get_full_configuration().unit_configuration.channel_3_and_4_config == ChanelConfig.GSM

        if index in (0, 1):
            return self.read_pilot_state_dma_e5(index)[0]
        elif not block_state:
            return await self._read_output_power(index, True, False)
                # s = [0] * 4; s[index - 2] = 1
                # await self.update_service_tx_waveforms(s)
                # return self.read_service_output_power_dmas(index-2)
        else:
            return 0.0

    def set_linx_tx_prev_aten(self, index, value):
        self._linx_tx_prev_aten[index] = value
        save_config('ac/linx_tx_prev_aten', self._linx_tx_prev_aten)

    def get_ra_clocks_measured(self) -> float:
        ra_clocks_measured = self._carrier_frequency_ac.ra_clocks_measured
        if ra_clocks_measured is None:
            return 40_000_000

        return ra_clocks_measured

    def set_output_power_correct_number(self, index: int, number: float):
        self.output_power_correct_number[index] = number
        save_const('ac/output_power_correct_number', self.output_power_correct_number)

    def set_additional_output_power_correct_number(self, index: int, number: float):
        self.additional_output_power_correct_number[index] = number
        save_const('ac/additional_output_power_correct_number', self.additional_output_power_correct_number)

    def set_loop_state(self, state: bool):
        self.loop_state = state
        save_config('ac/loop_state', state)

    def set_base_statin_calibration_state(self, state: bool):
        self.base_statin_calibration_state = state
        save_config('ac/base_statin_calibration_state', state)

    def set_target_power_delta_offsets(self, offsets: list[float]):
        self.target_power_delta_offsets = offsets
        for i in range(settings.DMA_POWER_METERS_NUMBER):
            save_config(f'ac/target_power_delta_offsets{i}', offsets[i])

    async def set_adjustment_state(self, state: bool):
        if state:
            await self.AD_API.set_gain(0, DoubleGain(gain_0=10, gain_1=10))
            await self.AD_API.set_attenuation(0, DoubleAttenuation(attenuation_0=10, attenuation_1=10))
        else:
            DevMem(address=0x43C6_0008).write(0, 0)
            DevMem(address=0x43C6_000C).write(0, 0)

        self.adjustment_state = state
        save_config('ac/adjustment_state', self.adjustment_state)

    async def cmg_noise(self):
        while True:
            await asyncio.sleep(1.7)
            async with self._cmg_lock:
                match random.randint(0, 6):
                    case 0:
                        await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                            ru='Это обычное отладочное сообщение',
                            en='This is a ordinary debug message'
                        ))
                    case 1:
                        await self.push_calibrate_message(CMK.Blessed, LanguageString(
                            ru='Это благословенное отладочное сообщение',
                            en='This is a blessed debug message'
                        ))
                    case 2:
                        await self.push_calibrate_message(CMK.Infernal, LanguageString(
                            ru='Это адское отладочное сообщение',
                            en='This is a Infernal debug message'
                        ))
                    case 3:
                        await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                            ru=f'Это обычное обновляемое отладочное сообщение. Попугаи: {0.0:.2f}',
                            en=f'This is a ordinary updated debug message. Parrots: {0.0:.2f}'
                        ))
                        for i in range(0, 39):
                            await asyncio.sleep(0.2)
                            await self.update_calibrate_message(CMK.Ordinary, LanguageString(
                                ru=f'Это обычное обновляемое отладочное сообщение. Попугаи: {float(i):.2f}',
                                en=f'This is a ordinary updated debug message. Parrots: {float(i):.2f}'
                            ))
                    case 4:
                        await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                            ru='Это обычное очень очень очень очень очень очень очень очень очень очень очень очень очень очень длинное отладочное сообщение',
                            en='This is the ordinary very very very very very very very very very very very very very very very very long debug message'
                        ))
                    case 5:
                        await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                            ru='Съешь ещё этих мягких французских булок, да выпей чаю',
                            en='Crazy Fredrick bought many very exquisite opal jewels'
                        ))
                    case 6:
                        await self.push_calibrate_message(CMK.Blessed, LanguageString(
                            ru='Богиня благословляет это отладочное сообщение',
                            en='Goddess bless this debug message'
                        ))

                self.last_calibrate_history.messages = self.last_calibrate_history.messages[-100:]
                self.last_calibrate_history.date = datetime.datetime.now()

    async def calibrate(
            self,
            sm_base_power: list[float], sm_cable_loss_dl: list[float], sm_state: list[bool],
            rm_output_power: list[int], rm_cable_loss: list[int], rm_state: list[bool],
            acc_base_power_calibrate: bool, acc_link_calibrate: bool, acc_tx_service_calibrate: bool,
            only_reach_the_power: bool
            ) -> bool:

        self.last_calibrate_history.messages.clear()
        self.is_stop_calibration = False

        self.calibration_in_progress = True
        self.calibration_start_time = time.monotonic()

        async with self._cmg_lock:
            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru='Начало калибровки',
                en='Start calibration'
            ))

            block_state = await self.CONTROL_API.get_control_state('block_state')
            self._all_loop_state = False
            save_config('ac/dma_off', True)
            os.system('systemctl stop rcs-dma.service')

            self.sm_base_power = sm_base_power
            self.sm_base_power_offsets = sm_base_power
            save_config('ac/sm_base_power', self.sm_base_power)
            save_config('ac/sm_base_power_offsets', self.sm_base_power_offsets)

            self.rm_base_power = rm_output_power
            self.rm_base_power_offsets = rm_output_power
            save_config('ac/rm_base_power', self.rm_base_power)
            save_config('ac/rm_base_power_offsets', self.rm_base_power_offsets)

            if any([self.link_rx_loop_state, self.link_tx_loop_state, self.service_tx_loop_state]):
                await asyncio.sleep(8.0)

            self.off_services_pilot()
            await asyncio.sleep(3.0)

            calibrate_statuses = []

            try:
                if block_state and acc_base_power_calibrate:
                    calibrate_statuses.append(
                        await self._base_power_calibrate(sm_base_power, sm_cable_loss_dl, sm_state)
                    )
            except Exception as e:
                #print(f'base_power_calibrate: {e}')
                calibrate_statuses.append(False)

            try:
                if acc_link_calibrate:
                    calibrate_statuses.append(
                        await self._link_calibrate(rm_output_power, rm_cable_loss, rm_state, only_reach_the_power)
                    )
            except Exception as e:
                #print(f'link_calibrate: {e}')
                calibrate_statuses.append(False)

            try:
                if not block_state and acc_tx_service_calibrate:
                    calibrate_statuses.append(
                        await self._tx_service_calibrate(sm_base_power, sm_state, only_reach_the_power)
                    )
            except Exception as e:
                #print(f'tx_service_calibrate: {e}')
                calibrate_statuses.append(False)

            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru='Завершение калибровки',
                en='Completing calibration'
            ))

            if not only_reach_the_power:
                await self.DSP_API.set_simulation_of_chain(False)

            self._all_loop_state = True
            os.system('systemctl start rcs-dma.service')
            save_config('ac/dma_off', False)
            await asyncio.sleep(18)

            async def enforce_tx_ac():
                if not block_state:
                    for _ in range(8):
                        await self._tx_services_control()
                        await asyncio.sleep(0.5)

            asyncio.create_task(enforce_tx_ac())

            self.last_calibrate_history.date = datetime.datetime.now()
            save_config('ac/last_calibrate_history', self.last_calibrate_history)

        self.calibration_start_time = None
        self.calibration_in_progress = False
        return all(calibrate_statuses)

    async def set_sm_base_power(self, sm_base_power: list[float]):
        self.sm_base_power = sm_base_power
        save_config('ac/sm_base_power', self.sm_base_power)

    async def run_service_tx_ac(self):
        async def ac_task():
            for _ in range(16):
                if self._all_loop_state and self.service_tx_loop_state:
                    await self._tx_services_control()
                    await asyncio.sleep(2.0)

        asyncio.create_task(ac_task())

    def set_special_bit(self, offset: int, state: bool):
        mem = DevMem(address=0x83C1_0000)
        mem[offset] = state

    async def rx_rm_watch(self):
        await asyncio.sleep(44)
        if await self.CONTROL_API.get_control_state('block_state'):
            return

        while True:
            await asyncio.sleep(4)
            try:
                if len(self._rx_rm_memory) < 4:
                    continue

                first = self._rx_rm_memory[0]
                max_def = max(
                    map(
                        lambda x: abs(x - first),
                        self._rx_rm_memory
                    )
                )

                if max_def > 2:
                    self._rx_rm_memory = []
                    if self._all_loop_state and self.service_tx_loop_state:
                        await self._tx_services_control()

            except Exception:
                await asyncio.sleep(13)

    async def tx_service_gsm_loop(self):
        await asyncio.sleep(66)

        block_state = await self.CONTROL_API.get_control_state('block_state')
        if block_state:
            return

        while True:
            await asyncio.sleep(4)
            try:
                if self._all_loop_state and self.service_tx_loop_state:
                    await self._tx_services_control_gsm()
            except Exception as e:
                pass

            await asyncio.sleep(20.2)

    async def rx_services_loop(self):
        await asyncio.sleep(40)

        if await self.CONTROL_API.get_control_state('block_state'):
            return

        up_counter_max = 0
        up_counter = [0, 0, 0, 0]

        async def control(index, my_clip: int, link_1_tx: int, link_2_rx: int, fu_config):
            ad_index = (1, 1, 2, 2)[index]
            gain_index = (1, 0, 1, 0)[index]
            ad_config = await self.AD_API.get_config(ad_index)
            current_gain = [ad_config.gain_0, ad_config.gain_1]

            if my_clip == -1:
                return False

            my_cl = my_clip >= 16
            link_1_tx_cl = link_1_tx >= 30
            link_2_rx_cl = link_2_rx >= 30

            action = True
            match (link_1_tx_cl, my_cl, link_2_rx_cl):
                case (True, True, False):
                    up_counter[index] = 0
                    current_gain[gain_index] -= 3
                case (True, False, True):
                    action = False
                case (True, False, False):
                    up_counter[index] = 0
                    current_gain[gain_index] -= 1
                case (False, True, False):
                    up_counter[index] = 0
                    current_gain[gain_index] -= 2
                case (True, True, True):
                    up_counter[index] = 0
                    current_gain[gain_index] -= 3
                case (False, False, _):
                    if up_counter[index] >= up_counter_max:
                        up_counter[index] = 0
                        current_gain[gain_index] += 1
                    else:
                        up_counter[index] += 1
                        action = False
                case _:
                    action = False

            if action:
                if 0 <= index <= 1:
                    await self.AD_API.set_gain(ad_index, DoubleGain(
                        gain_0=clamp(fu_config.service_channels[1].str.gain.ul, 1, current_gain[0]),
                        gain_1=clamp(fu_config.service_channels[0].str.gain.ul, 1, current_gain[1])
                    ))
                else:
                    await self.AD_API.set_gain(ad_index, DoubleGain(
                        gain_0=clamp(fu_config.service_channels[3].str.gain.ul, 1, current_gain[0]),
                        gain_1=clamp(fu_config.service_channels[2].str.gain.ul, 1, current_gain[1])
                    ))

        await self._reset_clipping()
        while True:
            await asyncio.sleep(16)
            try:
                if not self._all_loop_state or self.adjustment_state:
                    continue

                if self._all_loop_state and self.service_rx_loop_state:
                    config = await self.SI_API.get_full_configuration()
                    *sm_clipping, link1_tx, link2_rx, _, _ = self._read_clipping()

                    for sm_index in range(4):
                        await control(sm_index, sm_clipping[sm_index], link1_tx, link2_rx, config)

                    await self._reset_clipping()
            except:
                pass


    async def tx_service_loop(self):
        os.system('systemctl start rcs-dma.service')
        await asyncio.sleep(120)
        start_flag = True

        block_state = await self.CONTROL_API.get_control_state('block_state')
        prev_service_tx_loop_state = False
        if block_state:
            return

        while True:
            await asyncio.sleep(2)
            try:
                if not self._all_loop_state or self.adjustment_state:
                    await asyncio.sleep(16)
                    continue

                if not prev_service_tx_loop_state and self.service_tx_loop_state:
                    for _ in range(6):
                        try:
                            await self._tx_services_control()
                            await asyncio.sleep(10)
                        except Exception:
                            pass

                prev_service_tx_loop_state = self.service_tx_loop_state

                if self._all_loop_state and self.service_tx_loop_state:
                    if start_flag:
                        for _ in range(12):
                            await self._tx_services_control()
                            await asyncio.sleep(5)

                        start_flag = False

                    # logging.debug(f'tx_services_control begin')
                    await self._tx_services_control()
                    # logging.debug(f'tx_services_control end')

                await asyncio.sleep(12.2 * 60)
            except Exception as e:
                #print('tx_service_loop: {e}')
                pass

    async def find_pilot_by_power(self, index: int, central_freq: int | None = None, bandwidth=100_000):
            if await self._rm_is_ok(index):
                self._correct_pilot_in_work[index] = True
                result = await self._carrier_frequency_ac.find_pilot_by_power(self, index, central_freq, bandwidth)
                self._correct_pilot_in_work[index] = False
                return result

            return True

    async def carrier_frequency_log_loop(self):
        await asyncio.sleep(200)
        logger_ra_clock = new_logger('pss_clock', level=logging.ERROR, stdout_handler=False)

        while True:
            await asyncio.sleep(10)
            pss = self._carrier_frequency_ac.read_ra_clocks_measured_pss()
            if pss is not None:
                logger_ra_clock.info(f'{pss}')

    async def carrier_frequency_control_loop(self):
        await asyncio.sleep(88)

        # pre_gps_status = True
        await asyncio.gather(self.find_pilot_by_power(0, -26_000_000), self.find_pilot_by_power(1, -26_000_000))
        await self._carrier_frequency_control()

        carrier_frequency_counter = 0
        while True:
            await asyncio.sleep(1 * 60)
            try:
                if not self._all_loop_state:
                    await asyncio.sleep(8)
                    continue

                carrier_frequency_counter += 1
                if carrier_frequency_counter > 22:
                    await self._carrier_frequency_control()
                    carrier_frequency_counter = 0

                (result0, result1) = await asyncio.gather(self.find_pilot_by_power(0, None, 5_000),
                                     self.find_pilot_by_power(1, None, 5_000))

                if not result0:
                    await self.find_pilot_by_power(0, -26_000_000)

                if not result1:
                    await self.find_pilot_by_power(1, -26_000_000)

                await asyncio.sleep(30)
            except Exception as e:
                await asyncio.sleep(30)
                #print('carrier_frequency_control_loop: {e}')
                pass

    async def adjustment_loop(self):
        found = False
        while True:
            await asyncio.sleep(1.1)
            try:
                if self._all_loop_state and self.adjustment_state:
                    if not found:
                        found = await self.find_pilot_by_power(0, -26_000_000)
                    else:
                        self._pwm_control()
                else:
                    found = False
            except Exception as e:
                # print(f'adjustment loop {e}')
                pass

    async def rx_control_loop(self):
        while True:
            await asyncio.sleep(0.2)
            try:
                if any(self._correct_pilot_in_work):
                    continue

                if not self.adjustment_state:
                    # logging.debug(f'rx_control begin')
                    await self._rx_control()
                else:
                    for index in range(2):
                        self.read_pilot_power_e0_e1(index, averaging=True, target=44)

            except Exception as e:
                await asyncio.sleep(1)
                # print(f'rx control loop {e}')
                pass

    async def loop(self):
        async def restore_linx_tx_prev_aten():
            ad_index = 0
            for index in range(2):
                ad_config = await self.AD_API.get_config(ad_index)
                current_attenuation = [int(ad_config.attenuation_0), int(ad_config.attenuation_1)]

                start = current_attenuation[index]
                end = self._linx_tx_prev_aten[index]
                step = int(math.copysign(1, end - start))
                for at in range(start, end, step):
                    current_attenuation[index] += step
                    await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                        attenuation_0=current_attenuation[0], attenuation_1=current_attenuation[1]
                    ))
                    await asyncio.sleep(0.1)

        os.system('systemctl start rcs-dma.service')
        block_state = await self.CONTROL_API.get_control_state('block_state')

        await asyncio.sleep(33)
        if self._all_loop_state and self.link_tx_loop_state and not self.adjustment_state:
            await restore_linx_tx_prev_aten()

        while True:
            await asyncio.sleep(12.0)
            try:
                if self._all_loop_state:
                    if self.link_tx_loop_state and not self.adjustment_state:
                        # logging.debug(f'tx_control begin')
                        await self._tx_control()
                        # logging.debug(f'tx_control end')

                    await self.update_telemetry(block_state)

            except Exception as e:
                #print(f'loop {e}')
                pass

    async def update_telemetry(self, block_state: bool):
        if block_state:
            await self.update_current_power_base_station_telemetry()
        else:
            self.pilot_telemetry.sm_input_power = [
                await self._read_input_power(2 + i, True)
                for i in range(settings.SERVICES_MODULE_NUMBER)
            ]
            self.pilot_telemetry.sm_output_power = [
                await self._read_output_power(2 + i, True)
                for i in range(settings.SERVICES_MODULE_NUMBER)
            ]

        self.pilot_telemetry.pss_found = self._carrier_frequency_ac.pss_found

        # self.pilot_telemetry.pilots_power_rx = [self._read_pilot_power(0), self._read_pilot_power(1)]  # see read_pilot_power_e0_e1
        if not any(self._correct_pilot_in_work):
            self.pilot_telemetry.pilots_power_rx_link_status = [False] * 2
            self.pilot_telemetry.pilots_power_tx_link_status = [False] * 2
            for _ in range(15):
                await asyncio.sleep(0.1)
                if self._read_status_of_link(0):
                    self.pilot_telemetry.pilots_power_rx_link_status[0] = True

                if self._read_status_of_link(1):
                    self.pilot_telemetry.pilots_power_rx_link_status[1] = True

                if self.read_pilot_state_dma_e5(0)[1]:
                    self.pilot_telemetry.pilots_power_tx_link_status[0] = True

                if self.read_pilot_state_dma_e5(1)[1]:
                    self.pilot_telemetry.pilots_power_tx_link_status[1] = True

            # self.pilot_telemetry.pilots_power_rx_link_status = [self._read_status_of_link(0), self._read_status_of_link(1)]

            self.pilot_telemetry.pilots_power_tx = [self.read_pilot_state_dma_e5(0)[0], self.read_pilot_state_dma_e5(1)[0]]
            self.pilot_telemetry.sm_paps = self.sm_paps
        # self.pilot_telemetry.pilots_power_tx_link_status = [
        #     self.read_pilot_state_dma_e5(0)[1],
        #     self.read_pilot_state_dma_e5(1)[1]
        # ]

    async def get_current_output_power(self, index: int) -> float:
        def bpac_average_power_meter(ad_index, ch):
            time.sleep(0.5)
            read_count = 20
            average = 0.0
            for _ in range(20):
                with open(f"/sys/bus/iio/devices/iio:device{ad_index}/in_voltage{ch}_rssi") as file:
                    average += float(file.read().split()[0])
                    time.sleep(0.1)

            return average / read_count

        block_state = await self.CONTROL_API.get_control_state('block_state')
        if block_state and 2 <= index <= 5:
            ad_index = [1, 1, 2, 2][index-2]
            channel = [1, 0, 1, 0][index-2]
            p_in_curr_dbm = 17.5 - bpac_average_power_meter(ad_index, channel)  # Текущая мощность на входе AD в dBm
            return p_in_curr_dbm

        power = await self._read_output_power(index, True)
        if 0 <= index <= 1:
            power -= 1

        return float(power)

    async def update_current_power_base_station_telemetry(self):
        async def bpac_average_power_meter(ad_index, ch):
            time.sleep(0.5)
            read_count = 20
            average = 0.0
            all_data = [0.0] * 20
            for idx in range(20):
                with open(f"/sys/bus/iio/devices/iio:device{ad_index}/in_voltage{ch}_rssi") as file:
                    power = float(file.read().split()[0])
                    all_data[idx] = power
                    average += power
                    await asyncio.sleep(0.1)

            power = average / read_count
            return power, min(all_data), max(all_data)

        if not self.base_statin_calibration_state:
            return

        att_var = (await self.ATT_API.get_attenuators()).values
        for index in range(4):
            att_index = [1, 3, 5, 7][index]
            ad_index = [1, 1, 2, 2][index]
            channel = [1, 0, 1, 0][index]
            correct_number = 46 + self.additional_output_power_correct_number[index + 2]

            power, min_power, max_power = await bpac_average_power_meter(ad_index, channel)
            power = 17.5 - power + correct_number + att_var[att_index]
            min_power = 17.5 - min_power + correct_number + att_var[att_index]
            max_power = 17.5 - max_power + correct_number + att_var[att_index]
            self._logger_bs.debug(f'[BS-{index}] => {power}, max({min_power}), min({max_power})')
            self.pilot_telemetry.base_station_current_power[index] = min_power

    async def ocb_loop(self):
        async def control(index: int):
            ad_index = [1, 1, 2, 2][index]
            attenuation_index = [1, 0, 1, 0][index]

            if await self.get_ocb_state(index + 2):
                return

            ad_config = await self.AD_API.get_config(ad_index)
            current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]
            threshold = await self.OCB_API.get_ocb_threshold(f'service_{index}_ocb')
            current = getattr(await self.OCB_API.get_all_ocb_current_28v(), f'service_{index}_ocb')

            if (threshold - current) < 0.5:
                current_attenuation[attenuation_index] += 1

                await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                    attenuation_0=current_attenuation[0],
                    attenuation_1=current_attenuation[1]
                ))

        await asyncio.sleep(22)
        while True:
            await asyncio.sleep(0.8)
            try:
                for i in range(4):
                    await control(i)
            except Exception:
                pass

    async def _carrier_frequency_control(self):
        if self._carrier_frequency_control_enable:
            return await self._carrier_frequency_ac.carrier_frequency_control()
        else:
            return False

    # async def _off_service(self, index: int):
    #     config = await self.SI_API.get_full_configuration()
    #     config.service_channels[index].str.enabled = False
    #     await self.SI_API.set_full_configuration(config)

    def _pwm_control(self):
        pwmbase = DevMem(address=0x43C6_0004)
        pwmdl = DevMem(address=0x43C6_0008)
        pwmul = DevMem(address=0x43C6_000C)
        pwmbase.write(value=3300)

        def pp_corrector(pp, pwm):
            max_level_of_power = 45
            if pp > max_level_of_power:
                pp = max_level_of_power
            elif pp < max_level_of_power:
                pp = round(pp)
            value = 3300 - (max_level_of_power - pp) * 100
            if value < 0:
                value = 0
            pwm.write(value=value)
            return pp

        if self._read_status_of_link(0):
            ppdl = pp_corrector(self._read_pilot_power_max_ab(0), pwmdl)
        else:
            pwmdl.write(0, 0)
            ppdl = 0

        if self._read_status_of_link(1):
            ppul = pp_corrector(self._read_pilot_power_max_ab(1), pwmul)
        else:
            pwmul.write(0, 0)
            ppul = 0

        return ppdl, ppul

    async def set_rm_base_power(self, rm_base_power: list[float]):
        prev = self.rm_base_power
        self.rm_base_power = rm_base_power
        save_config('ac/rm_base_power', self.rm_base_power)

        async def control(index):
            ad_index = 0
            target_delta = self.rm_base_power[index] - prev[index]

            ad_config = await self.AD_API.get_config(ad_index)
            current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1][index]
            target_attenuation = self.clamp(current_attenuation[index] - target_delta, 6, 37)

            start_attenuation = int(min(current_attenuation, target_attenuation))
            end_attenuation = int(max(current_attenuation, target_attenuation))

            for at in range(start_attenuation, end_attenuation + 1):
                await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                    attenuation_0=current_attenuation[0], attenuation_1=current_attenuation[1]
                ))

                await asyncio.sleep(0.4)

        await control(0)
        await control(1)

    async def _tx_control(self):
        async def control(index: int):
            # self._logger_link.debug(f'[Control Tx {index + 1} BEGIN control]')

            if self.target_pilots_power[index] <= 0.0:
                await self.SI_API.raise_alarm(f'RTR{index+1}OutputPower', False, value=0.0)
                # self._logger_link.debug(f'[Control Tx {index + 1} SKIP target_pilots_power <= 0]')
                return

            if not await self._rm_is_ok(index):
                await self.SI_API.raise_alarm(f'RTR{index+1}OutputPower', False, value=0.0)
                # self._logger_link.debug(f'[Control Tx {index + 1} SKIP rm_is_ok]')
                return

            ad_index = 0

            temperature = await self.get_rm_temperate(index)
            if temperature is None:
                correction = 0.0
            else:
                def find_nearest(temp, table):
                    min_delta = (9999999, None)
                    for (k, v) in table.items():
                        delta = abs(temp - k)
                        if delta < min_delta[0]:
                            min_delta = (delta, v)

                    return min_delta[1]

                correction = find_nearest(temperature, RM_TX_TABLE)

            pilot_powers_offset = self.rm_base_power[index] - self.rm_base_power_offsets[index]
            current_power, link_status = self.read_pilot_state_dma_e5(index, True)
            target_power = self.target_pilots_power[index] + pilot_powers_offset + correction
            if current_power == 0.0 or not link_status:
                self._logger_link.debug(f'[Control Tx {index + 1}] -> SKIP current_power: {current_power}, link_status: {link_status}]')
                # logging.info(f'tx_control {index} -> skip')
                return

            ad_config = await self.AD_API.get_config(ad_index)
            current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]

            gps_status = self._carrier_frequency_ac.TELEMETRY_API.get_gps_telemetry().status
            current = getattr(await self.OCB_API.get_all_ocb_current_28v(), f'link_{index}_ocb')
            self._logger_link.debug(
                f'[Control Tx {index + 1} Status] -> '
                f'current: {current_power:.2f}, target: {target_power:.2f}, '
                f'gps: {gps_status}, clocks_measured: {self.get_ra_clocks_measured()}, '
                f'clocks_measured_on_last_cfc: {self._carrier_frequency_ac.ra_clocks_measured}, '
                f'attenuation: {current_attenuation[index]:.2f}, current: {current:.2f}'
            )

            delta = current_power - target_power
            # if abs(delta) > 25:
            #     self.SI_API.raise_alarm(f'RTR{index+1}OutputPower', value=delta)
            #     # logging.info(f'tx_control {index} -> skip: |delta| > 18, delta: {delta}')
            #     return

            # logging.info(
            #     f'tx_control {index} -> current_power: {current_power}, target_power: {target_power}, delta: {delta}'
            # )

            if math.copysign(1, self.tx_control_attenuation_lack[index]) != math.copysign(1, delta):
                self.tx_control_attenuation_lack[index] = 0

            if self.tx_control_attenuation_lack[index] == 0 and abs(delta) < 2.0:
                await self.SI_API.raise_alarm(f'RTR{index+1}OutputPower', False, value=delta)
                return

            if abs(delta) >= 2.0:
                self.tx_control_attenuation_lack[index] = int(delta)
                await self.SI_API.raise_alarm(f'RTR{index+1}OutputPower', False, value=delta)

            if abs(delta) >= 4.1:
                await self.SI_API.raise_alarm(f'RTR{index+1}OutputPower', value=delta)
            else:
                await self.SI_API.raise_alarm(f'RTR{index+1}OutputPower', False, value=delta)

            delta_step = abs(self.delta_step(delta))
            change = math.copysign(delta_step, delta)
            prev_current_attenuation = current_attenuation[index]
            current_attenuation[index] += math.copysign(delta_step, delta)
            self.tx_control_attenuation_lack[index] -= math.copysign(delta_step, self.tx_control_attenuation_lack[index])

            if not(3 <= current_attenuation[index] <= 45):
                if abs(delta) > 2.92:
                    await self.SI_API.raise_alarm(
                        f'TxLink{index+1}LoopOverAttenuation',
                        value=current_power
                    )
                else:
                    await self.SI_API.raise_alarm(f'TxLink{index + 1}LoopOverAttenuation', False, value=current_power)

                current_attenuation[index] = self.clamp(current_attenuation[index], 5, 38)
            else:
                await self.SI_API.raise_alarm(f'TxLink{index+1}LoopOverAttenuation', False, value=current_power)

            self._logger_link.debug(f'[Control Tx {index+1} Attenuation] -> {prev_current_attenuation:.2f} + {change:.2f} = {current_attenuation[index]:.2f}')

            self.set_linx_tx_prev_aten(index, int(current_attenuation[index]))
            await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                attenuation_0=current_attenuation[0], attenuation_1=current_attenuation[1]
            ))

        ad_index = 0
        ad_config = await self.AD_API.get_config(ad_index)
        current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]

        current_power, link_status = self.read_pilot_state_dma_e5(0, False)
        if current_attenuation[0] > 30 and not link_status:
            current_attenuation[0] = 30

        current_power, link_status = self.read_pilot_state_dma_e5(1, False)
        if current_attenuation[1] > 30 and not link_status:
            current_attenuation[1] = 30

        await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
            attenuation_0=current_attenuation[0], attenuation_1=current_attenuation[1]
        ))

        for index in range(2):
            await control(index)

    async def _rx_control(self):
        block_state = await self.CONTROL_API.get_control_state('block_state')
        target_power_level = [42, 42] if block_state else [38, 42]
        *_, link2_rx_clipping, link1_rx_clipping, _ = self._read_clipping()
        links_clipping = [link1_rx_clipping, link2_rx_clipping]
        ad_index = 0

        async def control(index: int):
            if not self._read_status_of_link(index):
                if links_clipping[index] >= 30:
                    config = await self.AD_API.get_config(0)
                    current_gain = [config.gain_0, config.gain_1]
                    current_gain[index] -= 3
                    current_gain[index] = self.clamp(current_gain[index], 4, 45)
                    await self.AD_API.set_gain(ad_index, DoubleGain(gain_0=current_gain[0], gain_1=current_gain[1]))
                return

            if not await self._rm_is_ok(index):
                return

            config = await self.AD_API.get_config(0)
            current_gain = [config.gain_0, config.gain_1]

            pilot_powers = self.read_pilot_power_e0_e1(index, averaging=True, target=target_power_level[index])
            if pilot_powers == 0.0:
                # logging.info(f'rx_control {index} -> skip')
                return

            self._logger_service.debug(f'[RX_LINK INFO {index}] -> pilot_powers: {pilot_powers:.2f}, target_power: {target_power_level[index]:.2f}, current_gain: {current_gain[index]:.2f}')
            delta = round(target_power_level[index] - pilot_powers)
            # if delta > 25:
            #     # logging.info(f'rx_control {index} -> skip: |delta| > 18, delta: {delta}')
            #     return

            if abs(delta) < 0.9 and delta > 0:
                await self.SI_API.raise_alarm(f'RxLink{index + 1}LoopOverGain', False, value=pilot_powers)
                return

            # logging.info(
            #     f'rx_control {index} -> pilot_powers: {pilot_powers}, target_power_level: {target_power_level[index]}'
            # )
            prev_gain = current_gain[index]
            if abs(delta) <= 3:
                current_gain[index] += math.copysign(1, delta)
            else:
                current_gain[index] += math.copysign(1, delta) * self.delta_step(delta)

            self._logger_service.debug(f'[RX_LINK CHANG GAIN {index}] -> Gain: {prev_gain:.2f} + {self.delta_step(delta):.2f} = {current_gain[index]:.2f}')

            if 1 <= current_gain[index] <= 45:
                await self.SI_API.raise_alarm(f'RxLink{index+1}LoopOverGain', False, value=pilot_powers)
                await self.AD_API.set_gain(ad_index, DoubleGain(gain_0=current_gain[0], gain_1=current_gain[1]))
            else:
                if abs(delta) >= 2.92:
                    await self.SI_API.raise_alarm(
                        f'RxLink{index+1}LoopOverGain',
                        value=pilot_powers
                    )
                else:
                    await self.SI_API.raise_alarm(f'RxLink{index + 1}LoopOverGain', False, value=pilot_powers)

                current_gain[index] = self.clamp(current_gain[index], 1, 45)
                await self.AD_API.set_gain(ad_index, DoubleGain(gain_0=current_gain[0], gain_1=current_gain[1]))

            if index == 0:
                self._rx_rm_memory.append(current_gain[index])
                self._rx_rm_memory = self._rx_rm_memory[-self.RX_RM_MEMORY_SIZE:]

        for rm_index in range(2):
            await control(rm_index)

    async def _tx_services_control(self, only_one_index: int = None):
        async def control(index: int):
            if await self.get_ocb_state(index + 2):
                # self.SI_API.raise_info(f'ServiceTx{index + 1}Skipped', True, False)
                return

            if self.target_power_delta[index] == 0.0:
                return

            control_reg_name = (
                'service_0_state', 'service_1_state', 'service_2_state', 'service_3_state'
            )[index]

            if not await self.CONTROL_API.get_control_state(control_reg_name):
                return

            ad_index = [1, 1, 2, 2][index]
            attenuation_index = [1, 0, 1, 0][index]

            ad_config = await self.AD_API.get_config(ad_index)
            current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]

            # temperature = self.get_sm_temperate(index)
            # target_power_temp = load_config(f'ac/target_power_temp_{index}', float, None)
            # if temperature is None or target_power_temp is None:
            #     correction = 0.0
            # else:
            #     def find_nearest(temp, table):
            #         min_delta = (9999999, None)
            #         for (k, v) in table.items():
            #             delta = abs(temp - k)
            #             if delta < min_delta[0]:
            #                 min_delta = (delta, v)
            #
            #         return min_delta[1]
            #
            #     temp_table = load_config(f'ac/temp_table', dict[float, float], {0: 0})
            #     correction = find_nearest(temperature, temp_table)

            target_power_offset = self.sm_base_power[index] - self.sm_base_power_offsets[index]
            target_power = self.target_power_delta[index] + target_power_offset
            status, current_power = await self.read_service_output_power_dmas(index)

            if not status:
                return

            threshold = await self.OCB_API.get_ocb_threshold(f'service_{index}_ocb')
            current = getattr(await self.OCB_API.get_all_ocb_current_28v(), f'service_{index}_ocb')
            # self._logger_service.debug(
            #     f'[Service AC Power {index}] => current_power: {current_power:.2f}, target: {target_power:.2f}, '
            #     f'input_power: {await self._read_input_power(2 + index, True):.2f}, '
            #     f'output_power: {await self._read_output_power(2 + index, True):.2f}, '
            #     f'attenuation: {current_attenuation[attenuation_index]:.2f}, current: {current}'
            # )

            delta = current_power - target_power
            self.sm_paps[index] = delta
            if current_power <= 12.0:
                # logging.info(f'tx_services_control {index} -> skip: current_power <= 27.0, current_power: {current_power}')
                return

            if abs(delta) < 0.91 and delta < 0:
                await self.SI_API.raise_alarm(
                    f'TxService{index + 1}LoopOverAttenuation', False,
                    value=current_power
                )
                return

            # logging.info(
            #     f'tx_services_control {index} -> current_power: {current_power}, target_power: {current_power}'
            # )
            if current_power < target_power:
                change = -1
            else:
                change = 1

            prev_attenuation = current_attenuation[attenuation_index]
            if (threshold - current) < 1.19 and change < 0:
                change *= 0.5
            else:
                change *= abs(self.delta_step_float(target_power - current_power, 0.25))

            current_attenuation[attenuation_index] += change
            if not (1 <= current_attenuation[attenuation_index] <= 30):
                if abs(delta) > 2.92:
                    await self.SI_API.raise_alarm(
                        f'TxService{index + 1}LoopOverAttenuation',
                        value=current_power
                    )
                else:
                    await self.SI_API.raise_alarm(
                        f'TxService{index + 1}LoopOverAttenuation', False,
                        value=current_power
                    )
                current_attenuation[attenuation_index] = self.clamp(current_attenuation[attenuation_index], 1, 30)
            else:
                await self.SI_API.raise_alarm(
                    f'TxService{index+1}LoopOverAttenuation', False,
                    value=current_power
                )

            if (threshold - current) < 0.5 and change < 0:
                await self.SI_API.raise_alarm(
                    f'TxService{index + 1}LoopOverAttenuation',
                    value=current_power
                )
            else:
                self._logger_service.debug(
                    f'[Service AC Attenuation {index}] => {prev_attenuation:.2f} + {change:.2f} = {current_attenuation[attenuation_index]:.2f}')

                await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                    attenuation_0=current_attenuation[0],
                    attenuation_1=current_attenuation[1]
                ))

        is_not_gsm_mode = \
            (await self.SI_API.get_full_configuration()).unit_configuration.channel_3_and_4_config != ChanelConfig.GSM

        if only_one_index is not None:
            s = [0] * 4
            s[only_one_index] = 1
            if not is_not_gsm_mode:
                s[2], s[3] = 0, 0
        else:
            s = [1, 1, 1, 1] if is_not_gsm_mode else [1, 1, 0, 0]

        if only_one_index is not None:
            if only_one_index in [0, 1] or is_not_gsm_mode:
                async with self._service_tx_control_lock:
                    await control(only_one_index)
            else:
                await asyncio.sleep(25.1)
        else:
            for index in range(4):
                if index in [0, 1] or is_not_gsm_mode:
                    async with self._service_tx_control_lock:
                        await control(index)

    async def tx_services_control_gsm_control(self, index: int, target_power: float, dma=True):
        control_reg_name = (
            'service_0_state', 'service_1_state', 'service_2_state', 'service_3_state', 'service_5v_state'
        )[index]

        if not await self.CONTROL_API.get_control_state(control_reg_name):
            return

        ad_index = [1, 1, 2, 2][index]
        attenuation_index = [1, 0, 1, 0][index]

        ad_config = await self.AD_API.get_config(ad_index)
        current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]

        target_power = target_power
        current_power = await self._read_output_power(2 + index, dma)

        threshold = await self.OCB_API.get_ocb_threshold(f'service_{index}_ocb')
        current = getattr(await self.OCB_API.get_all_ocb_current_28v(), f'service_{index}_ocb')
        # self._logger_service.debug(
        #     f'[Service AC Power {index}] => current_power: {current_power:.2f}, target: {target_power:.2f}, '
        #     f'input_power: {await self._read_input_power(2 + index, True):.2f}, '
        #     f'output_power: {await self._read_output_power(2 + index, True):.2f}, '
        #     f'attenuation: {current_attenuation[attenuation_index]:.2f}, current: {current:.2f}'
        # )

        delta = current_power - target_power
        if abs(delta) < 0.91 and delta < 0:
            await self.SI_API.raise_alarm(
                f'TxService{index + 1}LoopOverAttenuationGSM', False,
                value=current_power
            )
            return

        if current_power < target_power:
            change = -1
        else:
            change = 1

        prev_attenuation = current_attenuation[attenuation_index]
        if (threshold - current) < 1.09 and change < 0:
            change *= 0.5
        else:
            change *= abs(self.delta_step_float(target_power - current_power, 0.1, 3.0, 2.6))

        current_attenuation[attenuation_index] += change
        if not (1 <= current_attenuation[attenuation_index] <= 60):
            if abs(delta) > 2.92:
                await self.SI_API.raise_alarm(
                    f'TxService{index + 1}LoopOverAttenuationGSM',
                    value=current_power
                )
            else:
                await self.SI_API.raise_alarm(
                    f'TxService{index + 1}LoopOverAttenuationGSM', False,
                    value=current_power
                )
            current_attenuation[attenuation_index] = self.clamp(current_attenuation[attenuation_index], 1, 60)
        else:
            await self.SI_API.raise_alarm(
                f'TxService{index + 1}LoopOverAttenuationGSM', False,
                value=current_power
            )

        if (threshold - current) < 0.4 and change < 0:
            await self.SI_API.raise_alarm(
                f'TxService{index + 1}LoopOverAttenuationGSM',
                value=current_power
            )
        else:
            self._logger_service.debug(
                f'[Service AC Attenuation {index}] => {prev_attenuation:.2f} + {change:.2f} = {current_attenuation[attenuation_index]:.2f}')

            await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                attenuation_0=current_attenuation[0],
                attenuation_1=current_attenuation[1]
            ))

        return delta

    async def _tx_services_control_gsm(self):
        async with self._service_tx_control_lock:
            is_gsm_mode = \
                (await self.SI_API.get_full_configuration()).unit_configuration.channel_3_and_4_config == ChanelConfig.GSM

            for index in range(4):
                if index in (2, 3) and is_gsm_mode:
                    if not await self.get_ocb_state(index + 2):
                        target_power = (await self.SI_API.get_full_configuration()).service_channels[index].str.output_power
                        await self.tx_services_control_gsm_control(index, target_power)

    async def _base_power_calibrate(self,
                              sm_base_power: list[float], sm_cable_loss_dl: list[float], sm_state: list[bool]
                              ) -> bool:
        # def bpac_average_power_meter(ad_index, ch):v
        #     time.sleep(0.5)
        #     read_count = 20
        #     average = 0.0
        #     for _ in range(20):
        #         with open(f"/sys/bus/iio/devices/iio:device{ad_index}/in_voltage{ch}_rssi") as file:
        #             average += float(file.read().split()[0])
        #             time.sleep(0.1)
        #
        #     return average / read_count

        async def calibrate(index, base_power, cable_loss):
            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru=f'Калибровка по базовой станции {index+1}. Целевая мощность: {base_power}',
                en=f'Base station calibration {index+1}. Target power: {base_power}'
            ))

            att_index = [1, 3, 5, 7][index]
            ad_index = [1, 1, 2, 2][index]
            channel = [1, 0, 1, 0][index]

            att_var = await self.ATT_API.get_attenuators()
            att_var.values[att_index] = 0.0
            await self.ATT_API.set_attenuators(att_var)

            config = await self.AD_API.get_config(index=ad_index)
            # self.AD_API.set_gain(ad_index, DoubleGain(
            #     gain_0=[self.RX_GAIN, config.gain_0][channel],
            #     gain_1=[config.gain_1, self.RX_GAIN][channel]
            # ))
            # current_gains = [config.gain_0, config.gain_1]

            correct_number = 46 +\
                self.additional_output_power_correct_number[index+2]

            p_in_ad_max = base_power - correct_number - cable_loss  # Максимальнле значение на входе ЦОС в dBm
            p_over_range = abs(p_in_ad_max + 16) if p_in_ad_max > -16 else 0
            gain_over_range = -16 + abs(p_in_ad_max) if p_in_ad_max < -16 else 0

            att_var.values[att_index] = p_over_range
            await self.ATT_API.set_attenuators(config=att_var)

            rx_gain = 10 + gain_over_range + cable_loss
            await self.AD_API.set_gain(ad_index, DoubleGain(
                gain_0=[rx_gain, config.gain_0][channel],
                gain_1=[config.gain_1, rx_gain][channel]
            ))

            await self.push_calibrate_message(CMK.Blessed, LanguageString(
                ru=f'Калибровка по базовой станции {index+1} прошла успешно. Мощность: {base_power}',
                en=f'Base station calibration {index+1} was successful. Power: {base_power}'
            ))

            return True

        calibration_state = all([
            await calibrate(index, base_power, cable_loss) if state else True
            for index, (base_power, cable_loss, state) in enumerate(zip(sm_base_power, sm_cable_loss_dl, sm_state))
        ])

        self.set_base_statin_calibration_state(calibration_state)

        return calibration_state

    async def _tx_service_calibrate(self, out_power: list[float], sm_state: list[bool], only_reach_the_power: bool):
        async def calibrate(index: int, out_power: float):
            if only_reach_the_power:
                real_target_power = out_power
                real_target_power_delta = 0
            else:
                real_target_power = 38
                real_target_power_delta = out_power - real_target_power

            async def read_power_delta(offset=0):
                await asyncio.sleep(1)
                for _ in range(33):
                    try:
                        power = await self._read_output_power(power_index) + offset
                        return float(power - real_target_power), power
                    except BaseException:
                        await asyncio.sleep(5)

                return 36 - real_target_power, 36


            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru=f'Начало калибровки усилителя {index+1}',
                en=f'Starting service {index+1} calibration'
            ))
            self._logger_service.debug(f'[SM_CALIBRATE {index}] -> Start')

            ad_index = [1, 1, 2, 2][index]
            attenuation_index = [1, 0, 1, 0][index]
            power_index = index + 2
            bw = await self.get_sm_bw(index)
            power_offset = {
                20: 0,
                15: -1,
                10: -2,
                5: -3
            }.get(bw, 0)

            ad_config = await self.AD_API.get_config(ad_index)
            current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]
            current_attenuation[attenuation_index] = self.TX_SERVICE_ATTENUATION
            await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                attenuation_0=current_attenuation[0], attenuation_1=current_attenuation[1]
            ))
            if only_reach_the_power:
                self.set_special_bit(19, True)
                self.set_special_bit([9, 10, 11, 20][index], True)

            await self._sync_ad3(power_index)
            await asyncio.sleep(4)

            if not await self._sm_is_ok(index):
                await self.SI_API.raise_alarm(f'TxService{index+1}CalibrateSmIsNotOk', value=False)
                await self.push_calibrate_message(CMK.Infernal, LanguageString(
                    ru=f'Калибровка усилителя {index+1} прервана: некорректные показания напряжения',
                    en=f'Service {index+1} calibration aborted: incorrect voltage readings'
                ))
                return 0.0, None
            else:
                await self.SI_API.raise_alarm(f'TxService{index+1}CalibrateSmIsNotOk', False, value=True)

            delta, current_power = await read_power_delta(power_offset)
            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru=f'Достижение выходной мощности: {real_target_power + real_target_power_delta} на усилителе {index+1}. Текущая выходная мощность: {current_power + real_target_power_delta:.2f}. Текущий коэффициент усиление: {70 - current_attenuation[attenuation_index] + real_target_power_delta:.2f}',
                en=f'Reaching output power: {real_target_power + real_target_power_delta} on service {index+1}. Current output power: {current_power + real_target_power_delta:.2f}. Current gain: {70 - current_attenuation[attenuation_index] + real_target_power_delta}:.2f'
            ))
            while abs(delta) > 0.7:
                current_attenuation[attenuation_index] += math.copysign(1, delta) * self.delta_step(delta)
                if not (1 <= current_attenuation[attenuation_index] <= 30):
                    await self.SI_API.raise_alarm(f'TxService{index+1}CalibrateOverAttenuation', value=current_attenuation[attenuation_index] - self.clamp(current_attenuation[attenuation_index], 1, 30) + real_target_power_delta)
                    await self.push_calibrate_message(CMK.Infernal, LanguageString(
                        ru=f'Калибровка усилителя {index+1} прервана: для достижения выходной мощности {real_target_power + real_target_power_delta} не хватает коэффициента усиления',
                        en=f'Service {index+1} calibration aborted: there is not enough gain to achieve output power {real_target_power + real_target_power_delta}'
                    ))
                    return 0.0, None

                await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                    attenuation_0=current_attenuation[0], attenuation_1=current_attenuation[1]
                ))

                await asyncio.sleep(1.5)
                delta, current_power = await read_power_delta(power_offset)
                await self.update_calibrate_message(CMK.Ordinary, LanguageString(
                    ru=f'Достижение выходной мощности: {real_target_power + real_target_power_delta} на усилителе {index + 1}. Текущая выходная мощность: {current_power + real_target_power_delta:.2f}. Текущий коэффициент усиление: {70 - current_attenuation[attenuation_index] + real_target_power_delta:.2f}',
                    en=f'Reaching output power: {real_target_power + real_target_power_delta} on service {index + 1}. Current output power: {current_power + real_target_power_delta:.2f}. Current gain: {70 - current_attenuation[attenuation_index] + real_target_power_delta}:.2f'
                ))

            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru=f'Усилитель {index+1} достиг выходной мощности {current_power + real_target_power_delta:.2f} при коэффициенте усиления {70 - current_attenuation[attenuation_index] + real_target_power_delta:.2f}',
                en=f'Service {index+1} reached output power {current_power + real_target_power_delta:.2f} at gain {70 - current_attenuation[attenuation_index] + real_target_power_delta:.2f}'
            ))
            await self.SI_API.raise_alarm(f'TxService{index+1}CalibrateOverAttenuation', False, value=0.0)

            if only_reach_the_power:
                return -1.0, None

            # await self.push_calibrate_message(CMK.Ordinary, LanguageString(
            #     ru=f'Расчет окончательных попугаев усилителя {index+1}',
            #     en=f'Calculate final parrots of service {index+1}'
            # ))

            os.system('systemctl start rcs-dma.service')
            # save_config('ac/dma_off', False)
            await asyncio.sleep(8)

            powers = []
            for _ in range(7):
                status, read_power = await self.read_service_output_power_dmas(index)
                if status:
                    powers.append(read_power)
            try:
                powers = powers[2:]
            except Exception as e:
                pass

            power = float(np.mean(powers))

            # save_config('ac/dma_off', True)
            os.system('systemctl stop rcs-dma.service')
            await asyncio.sleep(1)

            self.current_power_delta[power_index] = await self._read_power_delta(power_index)

            await self.push_calibrate_message(CMK.Blessed, LanguageString(
                ru=f'Калибровка усилителя {index+1} прошла успешно. Выходная мощность: {current_power + real_target_power_delta:.2f}. Коэффициент усиление: {70 - current_attenuation[attenuation_index] + real_target_power_delta:.2f}',
                en=f'Service {index+1} calibration was successful. Output power: {current_power + real_target_power_delta:.2f}. Gain: {70 - current_attenuation[attenuation_index] + real_target_power_delta:.2f}'
            ))
            self._logger_service.debug(f'[SM_CALIBRATE {index}] -> Target Power: {power:.2f}, Attenuation: {current_attenuation[attenuation_index]:.2f}')

            temperature = await self.get_sm_temperate(index)
            return power + real_target_power_delta * 1.05, temperature

            # if abs(delta) <= 1.6:
            #     self.current_power_delta[power_index] = self._read_power_delta(power_index)
            #     self.SI_API.raise_alarm(
            #         f'TxService{index+1}CalibratePowerNotReached', False,
            #         value=self._read_output_power(power_index)
            #     )
            #
            #     return power
            # else:
            #     self.SI_API.raise_alarm(
            #         f'TxService{index+1}CalibratePowerNotReached',
            #         value=self._read_output_power(power_index)
            #     )
            #     return 0.0

        calibrate_status = []
        is_gsm_mode = \
            (await self.SI_API.get_full_configuration()).unit_configuration.channel_3_and_4_config == ChanelConfig.GSM\

        for index, (output_power, state) in enumerate(zip(out_power, sm_state)):
            temp = None
            if state and is_gsm_mode and index in [2, 3]:
                power_delta = output_power
                if only_reach_the_power:
                    delta = 0
                    for _ in range(11):
                        delta = await self.tx_services_control_gsm_control(index, output_power, False)
                        time.sleep(1.1)

                    if delta > 1.3:
                        power_delta = -1

                await self.push_calibrate_message(CMK.Blessed, LanguageString(
                    ru=f'Калибровка усилителя {index+1} прошла успешно',
                    en=f'Service {index + 1} calibration was successful'
                ))
            elif state and self.is_stop_calibration:
                power_delta = 0.0
                await self.push_calibrate_message(CMK.Infernal, LanguageString(
                    ru=f'Калибровка усилителя {index + 1} остановлена',
                    en=f'Service {index + 1} calibration stopped'
                ))
            elif state:
                power_delta, temp = await calibrate(index, output_power)
            else:
                power_delta = 0.0

            if not only_reach_the_power and state:
                self._set_target_power_delta(index, power_delta)
                if temp is not None:
                    save_config(f'ac/target_power_temp_{index}', temp)

            status = not state or power_delta != 0.0
            self._logger_service.debug(f'[SM_CALIBRATE {index}] -> End Status: {status}')
            calibrate_status.append(status)

        return all(calibrate_status)

    async def link_vswr_calibrate(self, index: int):
        def off_dma():
            self._all_loop_state = True
            os.system('systemctl start rcs-dma.service')
            save_config('ac/dma_off', False)

        target_power = 23

        self._all_loop_state = False
        save_config('ac/dma_off', True)
        os.system('systemctl stop rcs-dma.service')
        await asyncio.sleep(8.0)

        control_api = ControlApi()
        rm_api = RelaysModuleApi()

        link_control_name = f'link_{index}_state'
        link_state: bool = await control_api.get_control_state(link_control_name)
        await control_api.set_control_state(link_control_name, True)
        await asyncio.sleep(2.0)

        rm_output_power = [0, 0]
        rm_output_power[index] = target_power
        rm_state = [False, False]
        rm_state[index] = True
        try:
            calibrate_statuse = await self._link_calibrate(rm_output_power, [0, 0], rm_state, True)
        except Exception as e:
            off_dma()
            return None

        if not calibrate_statuse:
            off_dma()
            return None

        average = []
        await asyncio.sleep(3.1)
        for _ in range(6):
            average.append((await rm_api.get_telemetry(index)).rf_rev_detector_out)
            await asyncio.sleep(4.0)

        await self.DSP_API.set_simulation_of_chain(False)
        await control_api.set_control_state(link_control_name, link_state)

        off_dma()
        return sum(average) / len(average)

    async def _link_calibrate(
        self, rm_output_power: list[int], rm_cable_loss: list[int], rm_state: list[bool], only_reach_the_power: bool
    ) -> bool:
        block_state = await self.CONTROL_API.get_control_state('block_state')

        async def calibrate(index: int, output_power: int, cable_loss: int, is_gsm: bool):
            async def read_power_delta():
                time.sleep(1)
                target_power = output_power - cable_loss
                current_power = await self._read_output_power(power_index)
                return float(target_power - current_power), target_power, current_power

            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru=f'Начало калибровки радиоканала {index+1}',
                en=f'Starting radio channel {index+1} calibration'
            ))

            ad_index = 0
            power_index = [0, 1][index]
            attenuation_index = [0, 1][index]

            await self._sync_ad3(index)
            if only_reach_the_power:
                DevMem(address=self.GENERATOR_ADDR, number_of_words=32) \
                    .write(0, 0x188_0061 if block_state else [0x388_0020, 0x388_0060][index])
            elif not is_gsm:
                # включение симуляции в зависемоти от включенного свервисного канала
                DevMem(address=self.GENERATOR_ADDR, number_of_words=32)\
                    .write(0, 0x8_0061 if block_state else [0xC188_0020, 0xC188_0060][index])
            else:
                DevMem(address=self.GENERATOR_ADDR, number_of_words=32)\
                    .write(0, 0x8_0061 if block_state else [0x8_0020, 0x8_0060][index])

            ad_config = await self.AD_API.get_config(ad_index)
            current_attenuation = [ad_config.attenuation_0, ad_config.attenuation_1]
            await self.AD_API.set_attenuation(0, DoubleAttenuation(
                attenuation_0=self.TX_ATTENUATION,
                attenuation_1=self.TX_ATTENUATION
            ))
            current_attenuation[attenuation_index] = self.TX_ATTENUATION
            time.sleep(4)

            if not await self._rm_is_ok(index):
                await self.SI_API.raise_alarm(f'TxLink{index+1}CalibrateRmIsNotOk', value=False)
                await self.push_calibrate_message(CMK.Infernal, LanguageString(
                    ru=f'Калибровка радиоканала {index+1} прервана: некорректные показания напряжения',
                    en=f'Radio channel {index+1} calibration aborted: incorrect voltage readings'
                ))
                return 0.0
            else:
                await self.SI_API.raise_alarm(f'TxLink{index+1}CalibrateRmIsNotOk', False, value=False)

            delta, target_power, current_power = await read_power_delta()
            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru=f'Достижение выходной мощности: {target_power} на радиоканале {index+1}. Текущая выходная мощность: {current_power:.2f}. Текущий коэффициент усиление: {70 - current_attenuation[attenuation_index]:.2f}',
                en=f'Reaching power output: {target_power} on radio channel {index+1}. Current output power: {current_power:.2f}. Current gain: {70 - current_attenuation[attenuation_index]}:.2f'
            ))
            while abs(delta) > 0.7:
                current_attenuation[attenuation_index] -= math.copysign(1, delta) * self.delta_step(delta)
                if not (1 <= current_attenuation[attenuation_index] <= 50):
                    await self.SI_API.raise_alarm(f'TxLink{index+1}CalibrateOverAttenuation', value=current_attenuation[attenuation_index] - self.clamp(current_attenuation[attenuation_index], 1, 50))
                    await self.push_calibrate_message(CMK.Infernal, LanguageString(
                        ru=f'Калибровка радиоканала {index+1} прервана: для достижения выходной мощности {target_power} не хватает коэффициента усиления',
                        en=f'Radio channel {index+1} calibration aborted: there is not enough gain to reach output power {target_power}'
                    ))
                    return 0.0

                await self.AD_API.set_attenuation(ad_index, DoubleAttenuation(
                    attenuation_0=current_attenuation[0], attenuation_1=current_attenuation[1]
                ))

                time.sleep(1.5)
                delta, target_power, current_power = await read_power_delta()
                await self.update_calibrate_message(CMK.Ordinary, LanguageString(
                    ru=f'Достижение выходной мощности: {target_power} на радиоканале {index + 1}. Текущая выходная мощность: {current_power:.2f}. Текущий коэффициент усиление: {70 - current_attenuation[attenuation_index]:.2f}',
                    en=f'Reaching power output: {target_power} on radio channel {index + 1}. Current output power: {current_power:.2f}. Current gain: {70 - current_attenuation[attenuation_index]}:.2f'
                ))

            await self.push_calibrate_message(CMK.Ordinary, LanguageString(
                ru=f'Радиоканал {index+1} достиг выходной мощности {current_power:.2f} при коэффициенте усиления {70 - current_attenuation[attenuation_index]:.2f}',
                en=f'Radio channel {index+1} reached output power {current_power:.2f} at gain {70 - current_attenuation[attenuation_index]:.2f}'
            ))
            await self.SI_API.raise_alarm(f'TxLink{index+1}CalibrateOverAttenuation', False, value=0.0)

            if not self._read_status_of_link(2):
                await self.SI_API.raise_alarm(f'TxLink{index+1}CalibrateLinkLos', value=False)
                await self.push_calibrate_message(CMK.Infernal, LanguageString(
                    ru=f'Калибровка радиоканала {index+1} прервана: отсутствует выходной пилот сигнал',
                    en=f'Radio channel {index+1} calibration aborted: output pilot signal lost'
                ))
                return 0.0
            else:
                await self.SI_API.raise_alarm(f'TxLink{index+1}CalibrateLinkLos', False, value=True)

            if only_reach_the_power:
                return -1.0

            # await self.push_calibrate_message(CMK.Ordinary, LanguageString(
            #     ru=f'Расчет конечных попугаев радиоканала {index+1}',
            #     en=f'Calculation of the ultimate parrots of Radio channel {index+1}'
            # ))
            average = []
            for i in range(self.PILOT_POWER_AVERAGE_LEN * 5):
                average.append(self._read_pilot_power_max_ab(2))
                time.sleep(0.3)

            average = sorted(average)
            average = average[-3:]
            power = sum(average) / len(average)
            await self.push_calibrate_message(CMK.Blessed, LanguageString(
                ru=f'Калибровка радиоканала {index+1} прошла успешно. Выходная мощность: {current_power:.2f}. Коэффициент усиления: {70 - current_attenuation[attenuation_index]:.2f}',
                en=f'Radio channel {index+1} calibration was successful. Output power: {current_power:.2f}. Gain: {70 - current_attenuation[attenuation_index]:.2f}'
            ))

            if not block_state and index == 0:
                power += 4

            return power
            # power_delta = read_power_delta()
            # if abs(power_delta) <= 1.2 and self._rm_is_ok(index):
            #     average = []
            #     for i in range(self.PILOT_POWER_AVERAGE_LEN * 2):
            #         average.append(self._read_pilot_power(2))
            #         time.sleep(1.0)
            #
            #     self.SI_API.raise_alarm(
            #         f'TxLink{index+1}CalibratePowerNotReached', False,
            #         value=self._read_output_power(power_index)
            #     )
            #     return sum(average) / len(average)
            # else:
            #     self.SI_API.raise_alarm(
            #         f'TxLink{index+1}CalibratePowerNotReached',
            #         value=self._read_output_power(power_index)
            #     )
            #     return 0.0

        calibrate_status = []
        is_gsm_mode = \
            (await self.SI_API.get_full_configuration()).unit_configuration.channel_3_and_4_config == ChanelConfig.GSM

        for index, (output_power, cable_loss, state) in enumerate(zip(rm_output_power, rm_cable_loss, rm_state)):
            temp = None
            if state and self.is_stop_calibration:
                await self.push_calibrate_message(CMK.Infernal, LanguageString(
                    ru=f'Калибровка радиоканала {index+1} остановлена',
                    en=f'Radio channel calibration {index+1} stopped'
                ))
                pilot_power = 0.0
            elif state:
                pilot_power = await calibrate(index, int(output_power), int(cable_loss), is_gsm_mode)
                temp = await self.get_rm_temperate(index)
            else:
                pilot_power = 0.0

            if not only_reach_the_power and state:
                self._set_target_pilot_power(index, pilot_power)
                save_config(f'ac/target_pilot_temp_{index}', temp)

            calibrate_status.append(not state or pilot_power != 0.0)

            if block_state:
                DevMem(address=self.GENERATOR_ADDR, number_of_words=32)\
                    .write(0, 0x180_0061)

        return all(calibrate_status)

    async def _sm_is_ok(self, index):
        sm_telemetry = await self.SM_API.get_telemetry(index)
        return sm_telemetry.voltage_28v > 26 and sm_telemetry.lna_5v > 4.0

    async def _rm_is_ok(self, index):
        rm_telemetry = await self.RM_API.get_telemetry(index)
        return rm_telemetry.voltage_28v > 26 and rm_telemetry.voltage_8v > 6 and rm_telemetry.voltage_5v > 4.5

    def _set_target_pilot_power(self, index: int, value: float):
        self.target_pilots_power[index] = value
        save_config(f'ac/target_pilot_power_{index}', value)

    def _set_target_power_delta(self, index, value: float):
        self.target_power_delta[index] = value
        save_config(f'ac/target_power_delta{index}', value)

    async def _read_input_power(self, index: int, dma: bool = False):
        return (await self._read_power_dma(index))[0] if dma else (await self._read_power(index, False))

    async def _read_output_power(self, index: int, dma: bool = False, use_offset: bool = True):
        return (await self._read_power_dma(index, use_offset))[1] if dma else (await self._read_power(index, True, use_offset))

    async def _read_power_dma(self, index: int, use_offset = True):
        correct_number = self.output_power_correct_number[index] - self.additional_output_power_correct_number[index] - (await self.get_additional_correct_number2(index))
        if 0 <= index <= 1:
            correct_number += 1

        with open(f'{settings.ROOT_PATH}/environment/dma/power_meters', 'r') as file:
            data = file.read().split('\n')
            line = data[index]
            line1 = data[index]
            io = line.split(' ')
            io1 = line1.split(' ')
            return float(io1[0]), float(io[1]) - (correct_number * int(use_offset))

    async def get_additional_correct_number2(self, index: int) -> float:
        if index in (0, 1):
            return 0.0

        try:
            fuc = await self.SI_API.get_full_configuration()
            if fuc.unit_configuration.channel_3_and_4_config.value == 'GSM':
                cn = 0
            else:
                bandwidth = int(fuc.service_channels[index - 2].bandwidth / 1_000_000)
                cn = {
                    20: 0.0,
                    15: 0.5,
                    10: 1.0,
                    5: 1.3,
                }.get(bandwidth, 0.0)

            if index in (4, 5):
                cn + load_config(f'sm/pcn_{index-2}', int, 0)

            return cn
        except:
            return 0.0

    async def _read_power(self, index: int, is_out: bool, use_offset=True):
        def find_device():
            ctx = iio.Context()
            for dev in ctx.devices:
                if len(dev.buffer_attrs) > 0:
                    return dev

            return None

        def clac_average_power(data: np.ndarray):
            log_data = 10 * np.log10(data)
            return np.average(log_data[log_data > 0.9 * np.amax(log_data)])

        if is_out:
            reg = 17
        else:
            reg = [None, None, 13, 14, 15, 16][index]
            if reg is None:
                return 0.0

        dev: iio.Device = find_device()
        dev.set_kernel_buffers_count(1)
        for ch in dev.channels:
            ch.enabled = True

        buff = iio.Buffer(dev, settings.DMA_SAMPLES_NUMBER)
        self._mem.get(0x83C2_0000, 2)\
            .write(0, reg)

        pm = np.array([], dtype=np.uint32)
        for i in range(22):
            await asyncio.sleep(0.000_32)
            buff.refill()

            if i > 2:
                pm = np.concatenate([
                    pm,
                    np.frombuffer(buff.read(), np.uint32)[:settings.DMA_POWER_METERS_SAMPLES_NUMBER]
                ])

        correct_number = self.output_power_correct_number[index] - self.additional_output_power_correct_number[index] - (await self.get_additional_correct_number2(index))
        correct_number *= is_out and use_offset
        power = clac_average_power(pm) - correct_number
        return power

    async def _read_power_delta(self, index, dma: bool = False):
        delta = float(await self._read_input_power(index, dma) - (await self._read_output_power(index, dma)))
        if dma:
            current_delta = self.current_power_delta[index]
            if current_delta != 0.0:
                delta = current_delta + math.copysign(self.POWER_DELTA_STEP, delta - current_delta)
                self.current_power_delta[index] = delta
            else:
                self.current_power_delta[index] = delta

        return delta

    def _read_clipping(self) -> list[int]:
        try:
            with open('/home/debian/rcs/environment/dma/clipping', "rt") as clipping:
                data = clipping.read().split('\n')
                return [
                    int(data[0].strip()),
                    int(data[1].strip()),
                    int(data[2].strip()),
                    int(data[3].strip()),
                    int(data[4].strip()),
                    int(data[5].strip()),
                    int(data[6].strip()),
                    int(data[7].strip()),
                ]
        except:
            return [-1] * 8

    async def _reset_clipping(self):
        def reset():
            with open('/home/debian/rcs/environment/dma/clipping', "wt") as clipping:
                clipping.write('-1' + '\n-1' * 7)

        reset()
        for _ in range(2):
            await asyncio.sleep(0.5)
            reset()

    def _read_pilot_power_a(self, index: int):
        amp_a = self._pilot_field_select(self._read_pilot_word(index, 9), 0, 24)
        return 0 if amp_a == 0 else (math.log10(amp_a) * 10)

    def _read_pilot_power_b(self, index: int):
        amp_b = self._pilot_field_select(self._read_pilot_word(index, 10), 0, 24)
        return 0 if amp_b == 0 else (math.log10(amp_b) * 10)

    def _read_pilot_power_a_nl(self, index: int):
        amp_a = self._pilot_field_select(self._read_pilot_word(index, 9), 0, 24)
        return amp_a

    def _read_pilot_power_b_nl(self, index: int):
        amp_b = self._pilot_field_select(self._read_pilot_word(index, 10), 0, 24)
        return amp_b

    def _read_pilot_power_max_ab(self, index):
        amp_a = self._read_pilot_power_pa(index)
        amp_b = self._read_pilot_power(index)
        return max(amp_a, amp_b)

    def _read_pilot_power(self, index: int):
        amp_b = self._pilot_field_select(self._read_pilot_word(index, 8), 0, 24)
        return 0 if amp_b == 0 else (math.log10(amp_b) * 10)

    def _read_pilot_power_nl(self, index: int):
        amp_b = self._pilot_field_select(self._read_pilot_word(index, 8), 0, 24)
        return amp_b

    def _read_pilot_power_pa(self, index: int):
        amp_a = self._pilot_field_select(self._read_pilot_word(index, 7), 0, 24)
        return 0 if amp_a == 0 else (math.log10(amp_a) * 10)

    def _read_pilot_power_pa_nl(self, index: int):
        amp_a = self._pilot_field_select(self._read_pilot_word(index, 7), 0, 24)
        return amp_a

    def _read_pilot_threshold(self, index: int):
        threshold = self._pilot_field_select(self._read_pilot_word(index, 6), 0, 24)
        return threshold

    def _read_status_of_link(self, index: int) -> bool:
        link = self._pilot_field_select(self._read_pilot_word(index, 11), 10, 11)
        return link == 1

    def _read_pilot_word(self, index: int, offset: int):
        base_address = self.TCL[index]
        rdata = self._mem.get(base_address + offset * 4, 4).get_value(True)
        return rdata

    async def get_ocb_state(self, index) -> bool:
        return (await self.OCB_API.get_ocb_in_work_state())[index]

    async def _sync_ad3(self, index: int):
        ad_index = [0, 0, 1, 1, 2, 2][index]
        gain = [
            25, 25,
            load_config(f'sm/ad3_gain_0', int, 10),
            load_config(f'sm/ad3_gain_1', int, 10),
            load_config(f'sm/ad3_gain_2', int, 10),
            load_config(f'sm/ad3_gain_3', int, 10),
        ][index]

        config = await self.AD_API.get_config(ad_index)
        await self.AD_API.set_gain(3, DoubleGain(gain_0=gain, gain_1=gain))
        await self.AD_API.set_center_frequency(3, DoubleFrequency(
            rx_frequency=config.tx_frequency,
            tx_frequency=config.tx_frequency
        ))

        multiplexer_state = [4, 5, 3, 2, 1, 0][index]
        self._mem.get(self.SERVICE_CLOCK, 1)\
            .write(0, multiplexer_state)

    def read_pilot_state_dma_e5(self, index: int, averaging: bool = False):
        with open(f'{settings.ROOT_PATH}/environment/dma/pilot_states', 'r') as file:
            line = file.read().split('\n')[index]
            ps = line.split(' ')

            pilot_power = float(ps[0])
            link_status = ps[1] == 'true'
            if not link_status:
                return 0.0, False

            if averaging:
                pilot_power_average = self.pilot_power_average[index]
                pilot_power_average.append(pilot_power)

                pilot_power = 0.0
                if len(pilot_power_average) == self.PILOT_POWER_AVERAGE_LEN:
                    pilot_power = np.median(pilot_power_average)
                    self.pilot_power_average[index] = []

            return pilot_power, link_status

    def read_pilot_power_e0_e1(self, index: int, averaging: bool = False, **kwargs):
        pilot_power = self._read_pilot_power_max_ab(index)

        if averaging:
            average_index = index + 2
            self.pilot_power_average[average_index].append(pilot_power)

            pilot_power = 0.0
            if len(self.pilot_power_average[average_index]) == self.PILOT_POWER_AVERAGE_RX_AC_LEN:
                pws = [
                    self.pilot_power_average[average_index][i*6:(i+1)*6]
                    for i in range(5)
                ]
                pws = [
                    sum((
                        lambda m: list(map(lambda x: clamp(m+1.45, m-1.45, x), pp))
                    )(sum(pp) / len(pp)))
                    for pp in pws
                ]
                pilot_power = sum(pws) /  self.PILOT_POWER_AVERAGE_RX_AC_LEN
                self.pilot_telemetry.pilots_power_rx[index] = pilot_power
                self.pilot_power_average[average_index] = []
                
                target = kwargs['target']
                if (target - 1) <= pilot_power <= (target + 1):
                    return 0.0
                
            return pilot_power

    # async def read_service_output_power_iio(self, index: int) -> float | None:
    #     for _ in range(4):
    #         try:
    #             return self.call_function_isolate(await read_and_calc_iio_ppc, index)
    #         except Exception as e:
    #             # print(f'read_service_output_power_iio: {e}')
    #             continue
    #
    #     return None

    async def read_service_output_power_dmas(self, sm_index: int):
        async def update_service_tx_waveforms(index: int):
            s = [0, 0, 0, 0]
            s[index] = 1
            try:
                uri = f'http://localhost:3030/update-raw-services-out-power-waveforms/{s[0]}/{s[1]}/{s[2]}/{s[3]}'
                async with aiohttp.request('GET', uri) as response:
                    if response.status == 200:
                        body = await response.text()
                        papdma = list(map(float, body.split(' ')))
                        return True, papdma
            except Exception:
                return False, [0, 0, 0, 0]

        status, pap_dma = await update_service_tx_waveforms(sm_index)
        if pap_dma[sm_index] < 25:
            status, pap_dma = await update_service_tx_waveforms(sm_index)

        return status, pap_dma[sm_index]

    async def get_sm_temperate(self, index: int) -> float | None:
        try:
            temperature = [(await self.SM_API.get_telemetry(0)).temperature, (await self.SM_API.get_telemetry(1)).temperature, (await self.SM_API.get_telemetry(2)).temperature, (await self.SM_API.get_telemetry(3)).temperature]
            my_temperature = temperature.pop(index)
            avg = sum(temperature) / len(temperature)
            if abs(avg - my_temperature) > 28:
                return avg

            return my_temperature
        except Exception as e:
            return 25

    async def get_rm_temperate(self, index: int) -> float | None:
        try:
            temperature = [(await self.RM_API.get_telemetry(0)).temperature, (await self.RM_API.get_telemetry(1)).temperature]
            my_temperature = temperature.pop(index)
            avg = sum(temperature) / len(temperature)
            if abs(avg - my_temperature) > 28:
                return avg

            return my_temperature
        except Exception as e:
            return 33

    # async def clac_ppc(self, out_waveform, service_index: int):
    #     config = await self.SI_API.get_full_configuration()
    #
    #     bw = config.service_channels[service_index].bandwidth * 1_000_000
    #     if service_index in [2, 3]:
    #         if config.unit_configuration.channel_3_and_4_config.value == 'GSM':
    #             bw = 10_000_000
    #         elif config.unit_configuration.channel_3_and_4_config.value == 'LTE_GSM':
    #             bw += 10_000_000
    #
    #     rounder = 60_000_000 / 1024
    #     pilot_freq = -int((bw // 2 + 2_000_000) // rounder * rounder)
    #     return self.ppc.calc(out_waveform, 60_000_000, pilot_freq)

    def off_services_pilot(self):
        mem = DevMem(address=0x83C1_0000)
        for i in [1, 8, 3, 4]:
            mem[i] = False

    def services_pilot_set(self, index: int, state: bool):
        bin_index = [1, 8, 3, 4][index]
        DevMem(address=0x83C1_0000)\
            .read(0)[bin_index] = state

    async def push_calibrate_message(self, kind: CalibrateMessageKInd, msg: LanguageString = None):
        await self.send_calibrate_message(CalibrateMessageActionKind.Push, kind, msg)

    async def update_calibrate_message(self, kind: CalibrateMessageKInd, msg: LanguageString = None):
        await self.send_calibrate_message(CalibrateMessageActionKind.Update, kind, msg)

    async def send_calibrate_message(
        self, action_kind: CalibrateMessageActionKind, kind: CalibrateMessageKInd, msg: LanguageString = None
    ):
        if action_kind == CalibrateMessageActionKind.Push:
            self.last_calibrate_history.messages.append(
                CalibrateHistoryMessage(
                    kind=kind,
                    msg=msg,
                )
            )
        elif action_kind == CalibrateMessageActionKind.Update:
            self.last_calibrate_history.messages[-1] = CalibrateHistoryMessage(
                kind=kind,
                msg=msg,
            )

        await self.SI_API.set_calibrate_status(CalibrateMessage(
            action_kind=action_kind,
            kind=kind,
            msg=msg,
        ))

    async def get_sm_bw(self, index: int) -> int | float:
        try:
            config = await self.SI_API.get_full_configuration()
            return config.service_channels[index].bandwidth // 1_000_000
        except Exception:
            return 20

    @staticmethod
    def _pilot_field_select(word, start, end):
        l = end - start
        mask = (1 << l) - 1
        return (word >> start) & mask

    @staticmethod
    def clamp(num: float, min_value: float, max_value: float):
        return max(min(num, max_value), min_value)

    @staticmethod
    def delta_step(delta: float | int, low: float = 0.44, height: float = 3) -> int:
        delta = abs(delta)

        if delta < low:
            return 0
        elif low <= delta <= 1.9:
            return 1
        else:
            value = int(round(delta / 1.9))
            return height if value >= height else value

    @staticmethod
    def delta_step_float(delta: float | int, low: float = 0.44, low1: float = 1.9, divider: float = 1.9) -> float:
        delta = abs(delta)

        if delta < low:
            return 0
        elif low <= delta <= low1:
            return delta
        else:
            return delta / divider

    @staticmethod
    def call_function_isolate(function, *args, **kwargs):
        pool = Pool(1)
        result = pool.apply_async(function, args=args, kwds=kwargs)
        result = result.get(kwargs.get('timeout', 61))
        pool.close()
        pool.join()

        return result

    @staticmethod
    async def update_service_tx_waveforms(s: list):
        try:
            uri = f'http://localhost:3030/update-raw-services-out-power-waveforms/{s[0]}/{s[1]}/{s[2]}/{s[3]}'
            async with aiohttp.request('GET', uri) as response:
                if response.status == 200:
                    return True

        except Exception:
            pass

        return False


async def read_and_calc_iio_ppc(index: int):
    def find_device():
        ctx = iio.Context()
        for dev in ctx.devices:
            if len(dev.buffer_attrs) > 0:
                return dev

        return None

    def services_pilot_set(index: int, state: bool):
        bin_index = [1, 8, 3, 4][index]
        DevMem(address=0x83C1_0000)\
            .read(0)[bin_index] = state

    async def clac_ppc(out_waveform, service_index: int):
        si_api = ServerIntegrationApi()
        ppc = PilotPowerCalculation()
        config = await si_api.get_full_configuration()

        bw = config.service_channels[service_index].bandwidth * 1_000_000
        if service_index in [2, 3]:
            if config.unit_configuration.channel_3_and_4_config.value == 'GSM':
                bw = 10_000_000
            elif config.unit_configuration.channel_3_and_4_config.value == 'LTE_GSM':
                bw += 10_000_000

        rounder = 60_000_000 / 1024
        pilot_freq = -int((bw // 2 + 2_000_000) // rounder * rounder)
        return ppc.calc(out_waveform, 60_000_000, pilot_freq)

    services_pilot_set(index, True)
    time.sleep(0.51)
    dev: iio.Device = find_device()
    dev.set_kernel_buffers_count(1)
    for ch in dev.channels:
        ch.enabled = True

    buff = iio.Buffer(dev, settings.DMA_SAMPLES_NUMBER)
    DevMem(0x83C2_0000, word_size=4)\
        .write(0, 12)

    is_reade_reg = DevMem(address=0x83DF_0000)
    for _ in range(3):
        DevMem(0x83C1_0000, word_size=4)[2] = True
        DevMem(0x83C1_0000, word_size=4)[2] = False

        time.sleep(0.000_24)
        buff.refill()

    data = np.frombuffer(buff.read(), dtype=np.int16, count=2048)
    out_waveform = data[0::2] + 1j * data[1::2]
    power = float(await clac_ppc(out_waveform, index))
    services_pilot_set(index, False)
    return power

